""""""
import json
from pathlib import Path
from hashlib import md5
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import List, Dict
import pandas as pd

from peewee import (
    AutoField,
    CharField,
    DateTimeField,
    FloatField, IntegerField,
    Model,
    SqliteDatabase as PeeweeSqliteDatabase,
    ModelSelect,
    ModelDelete,
    chunked,
    ForeignKeyField,
    BooleanField,
    TextField
)

from vnsumscope.trader.constant import Exchange, Interval
from vnsumscope.trader.object import BarData, TickData, TradeData, OrderData
from vnsumscope.trader.utility import get_file_path

path = str(get_file_path("records.db"))
db = PeeweeSqliteDatabase(path, pragmas={'foreign_keys': 1})


class BaseModel(Model):
    class Meta:
        database = db


class Record(BaseModel):
    id = AutoField(primary_key=True)

    strategy_id = CharField(max_length=16)
    symbol = CharField(max_length=128)
    status = IntegerField(default=0)
    total_return = FloatField(null=True)

    create_time = DateTimeField(default=datetime.now())


class Parameter(BaseModel):
    record_id = ForeignKeyField(Record, backref='parameters', on_delete='CASCADE')

    vt_symbol = CharField(max_length=128)
    interval = CharField(max_length=64)
    start = DateTimeField()
    end = DateTimeField()
    rate = CharField(max_length=64)
    slippage = CharField(max_length=64)
    size = CharField(max_length=64)
    pricetick = CharField(max_length=64)
    capital = FloatField()
    mode = CharField(max_length=8)

    params = TextField()


class Statistic(BaseModel):
    record_id = ForeignKeyField(Record, backref='statistics', on_delete='CASCADE')

    start_date = DateTimeField()
    end_date = DateTimeField()
    total_days = IntegerField()
    profit_days = IntegerField()
    loss_days = IntegerField()
    capital = FloatField()
    end_balance = FloatField()
    max_drawdown = FloatField()
    max_ddpercent = FloatField()
    max_drawdown_duration = FloatField()
    total_net_pnl = FloatField()
    daily_net_pnl = FloatField()
    total_commission = FloatField()
    daily_commission = FloatField()
    total_slippage = FloatField()
    daily_slippage = FloatField()
    total_turnover = FloatField()
    daily_turnover = FloatField()
    total_trade_count = FloatField()
    daily_trade_count = FloatField()
    total_return = FloatField()
    annual_return = FloatField()
    daily_return = FloatField()
    return_std = FloatField()
    sharpe_ratio = FloatField()
    return_drawdown_ratio = FloatField()


class DailyResult(BaseModel):
    record_id = ForeignKeyField(Record, backref='daliy_reuslts', on_delete='CASCADE')

    date = DateTimeField()

    # close_price = FloatField()
    # pre_close = FloatField()

    close_price = CharField(max_length=255)
    pre_close = CharField(max_length=255)

    trade_count = FloatField()

    # start_pos = FloatField()
    # end_pos = FloatField()

    start_pos = CharField(max_length=255)
    end_pos = CharField(max_length=255)

    turnover = FloatField()
    commission = FloatField()
    slippage = FloatField()
    trading_pnl = FloatField()
    holding_pnl = FloatField()
    total_pnl = FloatField()
    net_pnl = FloatField()
    balance = FloatField()
    _return = FloatField(column_name='return')
    highlevel = FloatField()
    drawdown = FloatField()
    ddpercent = FloatField()


class Order(BaseModel):
    record_id = ForeignKeyField(Record, backref='orders', on_delete='CASCADE')

    orderid = IntegerField()
    symbol = CharField(max_length=128)
    exchange = CharField(max_length=16)
    type = CharField(column_name="type", max_length=4)
    direction = CharField(max_length=4)
    offset = CharField(max_length=4)
    price = FloatField()
    volume = FloatField()
    traded = FloatField()
    status = CharField(max_length=4)
    datetime = DateTimeField()
    gateway_name = CharField(max_length=16)


class Trade(BaseModel):
    record_id = ForeignKeyField(Record, backref='trades', on_delete='CASCADE')

    tradeid = IntegerField()
    orderid = IntegerField()
    symbol = CharField(max_length=128)
    exchange = CharField(max_length=16)
    direction = CharField(max_length=4)
    offset = CharField(max_length=4)
    price = FloatField()
    volume = FloatField()
    datetime = DateTimeField()
    gateway_name = CharField(max_length=16)


class BackTestStatus(Enum):
    RUNNING = 1
    SUCCESS = 2
    Failed = 3


class BackTestRecords():
    """操作回测历史记录"""

    def __init__(self) -> None:
        self.db = db
        self.db.connect()
        self.db.create_tables(
            [Record, Parameter, Statistic, DailyResult, Order, Trade]
        )

    def create_record(self, strategy_path: str, params: Dict) -> Record:
        # 获取策略名
        strategy_name = Path(strategy_path).stem
        # md5加密获得策略id
        strategy_id = md5(strategy_name.encode('utf8')).hexdigest()
        with self.db.atomic():
            record = Record(
                strategy_id=strategy_id,
                symbol=params['vt_symbol'],
                status=BackTestStatus.RUNNING.value,
                create_time=datetime.now()
            )
            record.save()
            self.save_parameter(record.id, params)
        return record

    @staticmethod
    def update_record(record_id: int, data: Dict):
        q = Record.update({
            Record.status: BackTestStatus.SUCCESS.value if data['status'] == 'success' else BackTestStatus.FAILED.value,
            Record.total_return: data['total_return']
        }).where(Record.id == record_id)
        q.execute()

    @staticmethod
    def get_records(strategy_id: int):
        records = Record.select().where(Record.strategy_id == strategy_id).dicts()
        records_list = []
        for records_obj in records:
            records_obj['symbol'] = ",".join(eval(records_obj['symbol']))
            records_list.append(records_obj)
        return records_list

    @staticmethod
    def delete_records(strategy_id: int) -> bool:
        try:
            Record.delete().where(Record.strategy_id == strategy_id).execute()
            return True
        except Exception as e:
            return False

    @staticmethod
    def get_record(record_id: int):
        record = Record.get_by_id(record_id)
        settings = record.parameters.dicts().first()
        # 字符串类型转字典类型，取values值列表
        settings['vt_symbol'] = ",".join(eval(settings['vt_symbol']))
        settings['rate'] = ','.join([str(rate_obj) for rate_obj in list(eval(settings['rate']).values())])
        settings['slippage'] = ','.join([str(slippage_obj) for slippage_obj in list(eval(settings['slippage']).values())])
        settings['size'] = ','.join([str(size_obj) for size_obj in list(eval(settings['size']).values())])
        settings['pricetick'] = ','.join([str(pricetick_obj) for pricetick_obj in list(eval(settings['pricetick']).values())])
        statistics = record.statistics.dicts().first()
        params = settings.pop('params')
        return {
            'settings': settings,
            'params': json.loads(params),
            'statistics': statistics
        }

    @staticmethod
    def save_parameter(record_id: int, params: Dict):
        p = Parameter(
            record_id=record_id,
            **params
        )
        p.save()

    def save_statistic(self, record_id: int, statistic: Statistic):
        s = Statistic(
            record_id=record_id,
            **statistic
        )
        s.save()

    def save_daily_result(self, record_id: int, daily_results: List[DailyResult], flag: str):
        daily_results.rename(columns={'return': '_return'}, inplace=True)
        if flag == 'portfolio':
            daily_results.reset_index(inplace=True)
            daily_results.insert(1, 'record_id', record_id)
            daily_results.rename(columns={'close_prices': 'close_price', 'end_poses': 'end_pos'}, inplace=True)
            daily_results['end_pos'] = daily_results['end_pos'].apply(lambda x: json.dumps(x))
            daily_results['close_price'] = daily_results['close_price'].apply(lambda x: json.dumps(x))
            daily_results.insert(2, 'pre_close', daily_results['close_price'])
            daily_results.insert(3, 'start_pos', daily_results['end_pos'])
        else:
            daily_results.drop(columns=['trades'], inplace=True)
            daily_results['close_price'] = daily_results['close_price'].apply(lambda x: str(x))
            daily_results['end_pos'] = daily_results['end_pos'].apply(lambda x: str(x))
            daily_results['start_pos'] = daily_results['end_pos'].apply(lambda x: str(x))
            daily_results['pre_close'] = daily_results['end_pos'].apply(lambda x: str(x))
            daily_results.reset_index(inplace=True)
            daily_results.insert(1, 'record_id', record_id)
        data = daily_results.to_dict(orient='records')
        with self.db.atomic():
            for c in chunked(data, 50):
                DailyResult.insert_many(c).execute()

    def get_daily_result(self, record_id: int):
        daily_result = DailyResult.select().where(DailyResult.record_id == record_id).dicts()
        result_df = pd.DataFrame([_ for _ in daily_result])
        result_df.drop(columns=['id', 'record_id'], inplace=True)
        result_df.rename(columns={'_return': 'return'}, inplace=True)
        result_df['date'] = pd.to_datetime(result_df['date']).dt.date
        result_df.set_index('date', inplace=True)
        result_df['trade_count'] = result_df['trade_count'].astype(int)
        return result_df

    def save_order(self, record_id: int, orders: List[OrderData]):
        data = []

        for order_obj in orders:
            d = order_obj.__dict__
            d["record_id"] = record_id
            d["direction"] = d["direction"].value
            d["exchange"] = d["exchange"].value
            d["offset"] = d["offset"].value
            d["type"] = d["type"].value
            d["status"] = d["status"].value
            d.pop("vt_orderid")
            d.pop("reference")
            d.pop("vt_symbol")
            data.append(d)
        with self.db.atomic():
            for c in chunked(data, 50):
                Order.insert_many(c).execute()

    def get_order(self, record_id: int):
        orders_query = Order.select().where(Order.record_id == record_id)
        orders = []
        for order_obj in orders_query:
            order_obj.volume = int(order_obj.volume)  # 转为int类型
            order_obj.traded = int(order_obj.traded)  # 转为int类型
            orders.append(order_obj)
        return orders

    def save_trade(self, record_id: int, trades: List[TradeData]):
        data = []

        for trade_obj in trades:
            d = trade_obj.__dict__
            d["record_id"] = record_id
            d["exchange"] = d["exchange"].value
            d["direction"] = d["direction"].value
            d["offset"] = d["offset"].value
            d.pop("vt_orderid")
            d.pop("vt_symbol")
            d.pop("vt_tradeid")
            data.append(d)
        with self.db.atomic():
            for c in chunked(data, 50):
                Trade.insert_many(c).execute()

    def get_trade(self, record_id: int):
        trades_query = Trade.select().where(Trade.record_id == record_id)
        trades = []
        for trade_obj in trades_query:
            trade_obj.volume = int(trade_obj.volume)  # 转为int类型
            trades.append(trade_obj)
        return trades


records_manager = BackTestRecords()
